package city.spring.configure.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Component;

/**
 * 自定义身份验证用户详细信息服务Impl
 *
 * @author HouKunLin
 * @date 2019/12/6 0006 15:27
 */
@Component
public class CustomAuthenticationUserDetailsServiceImpl implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
    private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationUserDetailsServiceImpl.class);
    private final boolean isDebug = logger.isDebugEnabled();
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken authentication) throws UsernameNotFoundException {
        UserDetails userDetails;
        userDetails = isUserDetails(authentication.getPrincipal());
        if (userDetails != null) {
            return userDetails;
        }
        OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(authentication.getName());
        if (oAuth2AccessToken == null) {
            throw new UsernameNotFoundException("Token失效");
        }
        if (oAuth2AccessToken.isExpired()) {
            throw new UsernameNotFoundException("Token过期，请尝试刷新Token或者重新获取Token");
        }
        // 在使用授权码登录时，这里会传入当前已经登录用户的Token信息，也就是 authentication 是一个Token
        OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(authentication.getName());
        if (oAuth2Authentication == null) {
            throw new UsernameNotFoundException("Token失效");
        }
        userDetails = isUserDetails(oAuth2Authentication.getPrincipal());
        if (userDetails != null) {
            return userDetails;
        }
        throw new UsernameNotFoundException("预授权找不到用户");
    }

    private UserDetails isUserDetails(Object principal) {
        if (principal instanceof UsernamePasswordAuthenticationToken) {
            // 这个条件被发现在启用 JwtAccessTokenConverter + InMemoryAuthorizationCodeServices 刷新 Token 时触发
            UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) principal;
            if (isDebug) {
                logger.debug("principal name '{}', principal {}, class {}",
                        authenticationToken.getName(), authenticationToken.getPrincipal(),
                        principal.getClass().getName());
            }
            return userDetailsService.loadUserByUsername(authenticationToken.getName());
        } else if (principal instanceof AbstractAuthenticationToken) {
            AbstractAuthenticationToken authenticationToken = (AbstractAuthenticationToken) principal;
            if (isDebug) {
                logger.debug("principal name '{}', principal {}, class {}",
                        authenticationToken.getName(), authenticationToken.getPrincipal(),
                        principal.getClass().getName());
            }
            return isUserDetails(authenticationToken.getPrincipal());
        } else if (principal instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) principal;
            if (isDebug) {
                logger.debug("principal name '{}', principal {}, class {}",
                        userDetails.getUsername(), principal, principal.getClass().getName());
            }
            return userDetails;
        }
        return null;
    }
}